package com.coincalf.service.impl;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.coincalf.dto.*;
import com.coincalf.entity.Coin;
import com.coincalf.entity.Config;
import com.coincalf.entity.Market;
import com.coincalf.entity.TradeArea;
import com.coincalf.framework.constants.Constant;
import com.coincalf.framework.dto.ForexMarketDTO;
import com.coincalf.framework.enums.*;
import com.coincalf.mapper.MarketMapper;
import com.coincalf.service.*;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.ExecutionException;

/**
 * <p>
 * 交易对配置信息 服务实现类
 * </p>
 *
 * @author qiang
 * @since 2018-05-12
 */
@Service
public class MarketServiceImpl extends ServiceImpl<MarketMapper, Market> implements MarketService, Constant {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private TurnoverOrderService turnoverOrderService;

    @Autowired
    private EntrustOrderService entrustOrderService;

    @Autowired
    private TradeAreaService tradeAreaService;

    @Autowired
    private CoinService coinService;

    @Autowired
    private ConfigService configService;

    @Autowired
    private UserFavoriteMarketService userFavoriteMarketService;

    /**
     * 查询所有有效的币币交易市场
     *
     * @param marketType 交易对类型
     * @return
     */
    @Override
    public List<MarketDTO> queryByType(MarketType marketType) {
        EntityWrapper<Market> wrapper = new EntityWrapper<>();
        wrapper.eq("type", marketType.getCode())                // 市场类型条件
                .eq("status", BaseStatus.EFFECTIVE.getCode())   // 状态条件
                .orderBy("sort", true);              // 排序
        List<Market> markets = baseMapper.selectList(wrapper);
        return MarketDTOMapper.INSTANCE.from(markets);
    }

    /**
     * 查询币币交易对行情
     *
     * @return
     */
    @Override
    public List<TradeAreaMarketDTO> queryTradeMarkets() {
        List<TradeAreaMarketDTO> marketData = new ArrayList<>();
        tradeAreaService.queryByType(TradeAreaType.DC_TYPE).forEach(tradeArea -> {
            TradeAreaMarketDTO tradeAreaMarket = this.queryTradeMarkets(tradeArea.getId());
            marketData.add(tradeAreaMarket);
        });
        return marketData;
    }

    /**
     * 查询币币交易对行情
     *
     * @param areaId 交易区域ID
     * @return
     */
    @Override
    public TradeAreaMarketDTO queryTradeMarkets(Long areaId) {
        TradeAreaMarketDTO tradeAreaMarket = new TradeAreaMarketDTO();
        TradeAreaDTO tradeArea = tradeAreaService.queryTradeAreaFromCache(areaId);
        tradeAreaMarket.setAreaName(tradeArea.getName());
        if (!StringUtils.isEmpty(tradeArea.getMarketIds())) {
            String[] ids = tradeArea.getMarketIds().split(",");
            List<Long> keys = new ArrayList<>();
            for (String id : ids) {
                keys.add(Long.valueOf(id));
            }
            ImmutableCollection<TradeMarketDTO> data = TRADE_MARKET_CACHE_INSTANCE().getAllPresent(keys).values();
            // 排序
            List<TradeMarketDTO> tradeMarkets = new ArrayList<>(data);
            Collections.sort(tradeMarkets);
            tradeAreaMarket.setMarkets(tradeMarkets);
        }
        return tradeAreaMarket;
    }

    /**
     * 查询用户收藏的币币交易市场
     *
     * @param userId 用户ID
     * @return
     */
    @Override
    public List<TradeAreaMarketDTO> queryFavoriteTradeMarkets(Long userId) {
        List<TradeAreaMarketDTO> marketData = new ArrayList<>();
        TradeAreaMarketDTO tradeAreaMarket = new TradeAreaMarketDTO();
        marketData.add(tradeAreaMarket);
        tradeAreaMarket.setAreaName("自选");
        List<Long> keys = new ArrayList<>();
        userFavoriteMarketService.queryUserFavoriteMarket(userId, MarketType.TRADE).forEach(userFavoriteMarket -> {
            keys.add(userFavoriteMarket.getMarketId());
        });
        ImmutableCollection<TradeMarketDTO> data = TRADE_MARKET_CACHE_INSTANCE().getAllPresent(keys).values();
        // 排序
        List<TradeMarketDTO> tradeMarkets = new ArrayList<>(data);
        Collections.sort(tradeMarkets);
        tradeAreaMarket.setMarkets(tradeMarkets);
        return marketData;
    }

    /**
     * 查询创新交易对行情
     *
     * @return
     */
    @Override
    public Collection<ForexMarketDTO> queryForexMarkets() {
        return FOREX_MARKET_CACHE_INSTANCE().asMap().values();
    }

    /**
     * 查询用户收藏的币币交易市场
     *
     * @param userId 用户ID
     * @return
     */
    @Override
    public List<ForexAreaMarketDTO> queryFavoriteForexMarkets(Long userId) {
        List<ForexAreaMarketDTO> marketData = new ArrayList<>();
        ForexAreaMarketDTO forexAreaMarket = new ForexAreaMarketDTO();
        marketData.add(forexAreaMarket);
        forexAreaMarket.setAreaName("自选");
        List<Long> keys = new ArrayList<>();
        userFavoriteMarketService.queryUserFavoriteMarket(userId, MarketType.FOREX).forEach(userFavoriteMarket -> {
            keys.add(userFavoriteMarket.getMarketId());
        });
        forexAreaMarket.setMarkets(FOREX_MARKET_CACHE_INSTANCE().getAllPresent(keys).values());
        return marketData;
    }

    /**
     * ForexAreaMarketDTO
     * 按交易区域查询所有有效的交易市场（缓存2分钟）
     *
     * @param areaId 交易区域ID
     * @return
     */
    @Override
    public List<Market> queryByTradeArea(long areaId) {
        EntityWrapper<Market> wrapper = new EntityWrapper<>();
        wrapper.eq("trade_area_id", areaId)                     // 市场类型条件
                .eq("status", BaseStatus.EFFECTIVE.getCode())   // 状态条件
                .orderBy("sort", true);              // 排序
        return baseMapper.selectList(wrapper);
    }

    /**
     * 根据交易对标识符查询交易对（缓存2分钟）
     *
     * @param symbol 交易对标识符
     * @return
     */
    @Override
    public Market queryBySymbol(String symbol) {
        EntityWrapper<Market> wrapper = new EntityWrapper<>();
        wrapper.eq("symbol", symbol);
        wrapper.eq("status", BaseStatus.EFFECTIVE.getCode());
        List<Market> markets = baseMapper.selectList(wrapper);
        if (CollectionUtils.isEmpty(markets)) {
            return null;
        }
        return markets.get(0);
    }

    /**
     * 币币交易市场缓存实例
     *
     * @return
     */
    @Override
    public LoadingCache<Long, TradeMarketDTO> TRADE_MARKET_CACHE_INSTANCE() {
        return TRADE_MARKET_CACHE;
    }

    private LoadingCache<Long, TradeMarketDTO> TRADE_MARKET_CACHE = CacheBuilder.newBuilder()
            .build(new CacheLoader<Long, TradeMarketDTO>() {
                @Override
                public TradeMarketDTO load(Long marketId) {
                    Market market = getMarketById(marketId);
                    return initTradeMarket(market);
                }
            });


    /**
     * 创新交易市场缓存实例
     *
     * @return
     */
    @Override
    public LoadingCache<Long, ForexMarketDTO> FOREX_MARKET_CACHE_INSTANCE() {
        return FOREX_MARKET_CACHE;
    }

    private LoadingCache<Long, ForexMarketDTO> FOREX_MARKET_CACHE = CacheBuilder.newBuilder()
            .build(new CacheLoader<Long, ForexMarketDTO>() {
                @Override
                public ForexMarketDTO load(Long marketId) {
                    Market market = getMarketById(marketId);
                    return initForexMarket(market);
                }
            });

    /**
     * 查询交易市场（缓存）
     *
     * @param marketId
     * @return
     */
    @Override
    public Market getMarketById(Long marketId) {
        return baseMapper.selectById(marketId);
    }

    /**
     * 基础货币兑（USDT或BTC）汇率
     *
     * @param market 交易对
     * @return
     */
    @Override
    public BigDecimal getBaseToCoinRate(Market market) {
        BigDecimal baseRate;
        ForexMarketDTO forexMarket = this.getMarketDataById(market.getId());
        if (Optional.ofNullable(forexMarket).isPresent()) {
            baseRate = forexMarket.getAskPrice();
        } else {
            baseRate = market.getOpenPrice();
        }
        TradeArea tradeArea = tradeAreaService.selectById(market.getTradeAreaId());
        // 报价货币
        if (!market.getBuyCoinId().equals(tradeArea.getCoinId())) {
            baseRate = BigDecimal.ONE;
        }
        return baseRate;
    }

    /**
     * 报价货币兑（USDT或BTC）汇率
     *
     * @param market 交易对
     * @return
     */
    @Override
    public BigDecimal getPriceToCoinRate(Market market) {
        BigDecimal baseRate;
        ForexMarketDTO marketData = this.getMarketDataById(market.getId());
        if (Optional.ofNullable(marketData).isPresent()) {
            baseRate = BigDecimal.ONE.divide(marketData.getAskPrice(), 20, BigDecimal.ROUND_UP);
        } else {
            baseRate = BigDecimal.ONE.divide(market.getOpenPrice(), 20, BigDecimal.ROUND_UP);
        }
        TradeArea tradeArea = tradeAreaService.selectById(market.getTradeAreaId());
        // 报价货币
        if (market.getBuyCoinId().equals(tradeArea.getCoinId())) {
            baseRate = BigDecimal.ONE;
        }
        return baseRate;
    }

    /**
     * 计算人民币价格
     *
     * @param market 交易对
     * @return
     */
    @Override
    public BigDecimal getCnyPrice(Market market) {
        Config config = configService.queryByTypeAndCode(CONFIG_TYPE_CNY, CONFIG_CNY2USDT);
        if (config == null || StringUtils.isEmpty(config.getValue())) {
            return BigDecimal.ZERO;
        }
        BigDecimal usdt2cnyRate = new BigDecimal(config.getValue());
        long priceCoinId = market.getBuyCoinId();
        Coin priceCoin = coinService.queryById(priceCoinId);
        if (priceCoin.getName().equals("USDT")) {
            // 非USDT交易对 人民币价格 = 当前价  * USDT对人民币费率
            return usdt2cnyRate.setScale(2, BigDecimal.ROUND_UP);
        }
        List<MarketDTO> marketDTOList = this.queryByType(MarketType.TRADE);
        for (MarketDTO marketDTO : marketDTOList) {
            long buyCoinId = marketDTO.getBuyCoinId();
            long sellCoinId = marketDTO.getSellCoinId();
            Coin buyCoin = coinService.queryById(buyCoinId);
            if (!"USDT".equals(buyCoin.getName())) {
                continue;
            }
            if (priceCoinId != sellCoinId) {
                continue;
            }
            BigDecimal usdtPrice = turnoverOrderService.queryCurrentPrice(marketDTO.getId());
            return usdtPrice.multiply(usdt2cnyRate).setScale(2, BigDecimal.ROUND_UP);
        }
        return BigDecimal.ZERO;
    }

    @Override
    public ImmutableList<TradeMarketDTO> queryByType(MarketType trade, String marketIds) {
        String[] ids = marketIds.split(",");
        List<Long> keys = new ArrayList<>();
        for (String id : ids) {
            keys.add(Long.valueOf(id));
        }

        ImmutableMap<Long, TradeMarketDTO> tradeMarketDTOImmutableMap = TRADE_MARKET_CACHE_INSTANCE().getAllPresent(keys);
        return tradeMarketDTOImmutableMap.values().asList();
    }

    /**
     * 初始化创新交易市场数据
     *
     * @param market 交易对
     */
    @Override
    public ForexMarketDTO initForexMarket(Market market) {
        BigDecimal price = turnoverOrderService.queryCurrentPrice(market.getId());
        List<BigDecimal> depth = entrustOrderService.selectForexDepth(market.getId());
        ForexMarketDTO forexMarket = new ForexMarketDTO();
        forexMarket.setMarketId(market.getId())
                .setTradeAreaId(market.getTradeAreaId())
                .setSymbol(market.getSymbol())
                .setName(market.getName())
                .setPrice(price)
                .setBidPrice(depth.get(0))
                .setAskPrice(depth.get(1))
                .setContractUnit(market.getContractUnit())
                .setChange(0D);
        if (depth.get(0).compareTo(BigDecimal.ZERO) == 0) {
            forexMarket.setBidPrice(market.getOpenPrice());
        }
        if (depth.get(1).compareTo(BigDecimal.ZERO) == 0) {
            forexMarket.setAskPrice(market.getOpenPrice());
        }
        // 点差 = （买入报价 - 卖出报价） / 点
        BigDecimal spread = (forexMarket.getBidPrice().subtract(forexMarket.getAskPrice()))
                .divide(market.getPointValue(), market.getPriceScale(), BigDecimal.ROUND_UP);
        forexMarket.setSpread(spread);
        BigDecimal baseMargin = this.getBaseMargin(market, forexMarket.getAskPrice());
        forexMarket.setBaseMargin(baseMargin);

        // 计算涨跌幅
        // 获取交易对K线
        String redisKey = new StringBuffer(REDIS_KEY_FOREX_KLINE)
                .append(market.getSymbol())
                .append(":").append(KlineType.ONE_DAY.getValue()).toString();
        List<String> jsonLineList = redisTemplate.opsForList().range(redisKey, 0L, 0L);
        if (!CollectionUtils.isEmpty(jsonLineList)) {
            // 已缓存的最新K线数据
            Line line = new Line(jsonLineList.get(0));
            // 张跌幅 = （当前价 - 前一日的收盘价） / 前一日的收盘价  * 100%；
            // 前一日收盘价即今日开盘价
            BigDecimal change = price.subtract(line.getOpen())
                    .divide(line.getOpen(), 4, BigDecimal.ROUND_HALF_UP)
                    .multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_UP);
            forexMarket.setChange(change.doubleValue());
        }
        return forexMarket;
    }

    /**
     * 获取保证金基础汇率
     * 每一手所需要的占用保证金，跟随汇率的波动而变动。（参考数据，如果计算会影响平台速度的话，可以设一计算频率）
     * 保证金 = 合约单位 * baseRate(基础货币兑美元汇率) * 保证金比例 * 委托量
     * 如果报价币种 != 结算币种，则:baseRate = 1
     * 如果报价币种 == 算币种，则：baseRate = 卖一价
     *
     * @param market 交易对
     * @param price  当前卖一价
     * @return
     */
    @Override
    public BigDecimal getBaseMargin(Market market, BigDecimal price) {
        // 计算基础保证金（1手保证金）
        /* 每一手所需要的占用保证金，跟随汇率的波动而变动。（参考数据，如果计算会影响平台速度的话，可以设一计算频率）
           保证金 = 合约单位 * baseRate(基础货币兑美元汇率) * 保证金比例 * 委托量
           如果报价币种 != 结算币种，则:baseRate = 1
           如果报价币种 == 算币种，则：baseRate = 卖一价
        */
        // 基础货币对(USDT/BTC)兑换率
        BigDecimal baseRate = price;
        TradeArea tradeArea = tradeAreaService.selectById(market.getTradeAreaId());
        // 报价货币
        if (!market.getBuyCoinId().equals(tradeArea.getCoinId())) {
            baseRate = BigDecimal.ONE;
        }
        BigDecimal baseMargin = new BigDecimal(market.getContractUnit())    // 合约单位
                .multiply(baseRate)                                         // * 基础货币对(USDT/BTC)兑换率
                .multiply(market.getMarginRate());                          // * 保证金比例
        return baseMargin;
    }

    /**
     * 获取交易对价格信息
     *
     * @param marketId 交易Id
     * @return
     */
    @Override
    public ForexMarketDTO getMarketDataById(Long marketId) {
        try {
            return FOREX_MARKET_CACHE.get(marketId);
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 初始化交易对数据
     *
     * @param market
     * @return
     */
    public TradeMarketDTO initTradeMarket(Market market) {
        TradeMarketDTO tradeMarket = new TradeMarketDTO();
        tradeMarket.setSymbol(market.getSymbol())       // 交易对标识符
                .setName(market.getName())              // 交易对名称
                .setImage(market.getImg())              // 交易对图片
                .setSort(market.getSort())              // 显示顺序
                .setBuyFeeRate(market.getFeeBuy())      // 买入手续费
                .setSellFeeRate(market.getFeeSell())    // 卖出手续费
                .setPriceScale(market.getPriceScale())  // 价格小数位数
                .setNumScale(market.getNumScale())      // 数量小数位数
                .setNumMin(market.getNumMin())          // 最小委托数量
                .setNumMax(market.getNumMax())          // 最大委托数量
                .setTradeMax(market.getTradeMax())      // 最大成交
                .setTradeMin(market.getTradeMin())      // 最小成交额
                .setPrice(market.getOpenPrice())        // 当前价
                .setCnyPrice(market.getOpenPrice())     // 当前价CNY价格
                .setCoinCnyPrice(market.getOpenPrice()) // 报价币种人民币价格
                .setHigh(market.getOpenPrice())         // 最高价
                .setLow(market.getOpenPrice())          // 最低价
                .setMergeDepth(this.getMergeDepth(market.getMergeDepth())) // 合并深度
                .setVolume(BigDecimal.ZERO)             // 日总交易量
                .setAmount(BigDecimal.ZERO)             // 日成交总额
                .setChange(0);                          // 涨跌幅
        // 最新成交价
        BigDecimal price = turnoverOrderService.queryCurrentPrice(market.getId());
        long priceCoinId = market.getBuyCoinId();
        Coin priceCoin = coinService.queryById(priceCoinId);
        BigDecimal coinCnyPrice = this.getCnyPrice(market);
        // 设置最新成交价，价格单位，对应CNY价格
        tradeMarket.setPrice(price)
                .setPriceUnit(priceCoin.getName())
                .setCnyPrice(coinCnyPrice.multiply(price).setScale(2, BigDecimal.ROUND_HALF_UP))
                .setCoinCnyPrice(coinCnyPrice);
        // 获取24H成交数量
        TurnoverData24HDTO turnoverData24H = turnoverOrderService.query24HDealData(market.getId());
        if (turnoverData24H != null) {
            tradeMarket.setVolume(turnoverData24H.getVolume())  // 24小时成交量
                    .setAmount(turnoverData24H.getAmount());    // 24小时成交额
        }
        // 获取交易对K线
        String redisKey = new StringBuffer(REDIS_KEY_TRADE_KLINE)
                .append(market.getSymbol())
                .append(":").append(KlineType.ONE_DAY.getValue()).toString();
        List<String> jsonLineList = redisTemplate.opsForList().range(redisKey, 0L, 0L);
        if (!CollectionUtils.isEmpty(jsonLineList)) {
            // 已缓存的最新K线数据
            Line line = new Line(jsonLineList.get(0));
            // 张跌幅 = （当前价 - 前一日的收盘价） / 前一日的收盘价  * 100%；
            // 前一日收盘价即今日开盘价
            BigDecimal change = price.subtract(line.getOpen())
                    .divide(line.getOpen(), 4, BigDecimal.ROUND_HALF_UP)
                    .multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_UP);
            tradeMarket.setHigh(line.getHigh())
                    .setLow(line.getLow())
                    .setChange(change.doubleValue());
        }
        return tradeMarket;
    }

    /**
     * 合并深度
     *
     * @return
     */
    private List<MergeDepthDTO> getMergeDepth(String mergeDepth) {
        if (StringUtils.isEmpty(mergeDepth)) {
            return null;
        }
        List<MergeDepthDTO> mergeDepthList = new ArrayList<>();
        String[] mergeDepthArray = mergeDepth.split(",");
        for (int i = 0; i < mergeDepthArray.length; i++) {
            MergeDepthDTO mergeDepthDTO = new MergeDepthDTO();
            mergeDepthList.add(mergeDepthDTO);
            mergeDepthDTO.setMergeType(DepthMergeType.getByValue(i).getCode());
            int scale = Integer.parseInt(mergeDepthArray[i]);
            BigDecimal number = new BigDecimal(Math.pow(10, scale));
            BigDecimal value = BigDecimal.ONE.divide(number, scale, BigDecimal.ROUND_HALF_UP);
            mergeDepthDTO.setValue(value);
        }
        return mergeDepthList;
    }
}
